feat(opencode-plugin): pin the SSH gateway host key from the Daytona API by default - #59
Merged
Merged
Conversation
…API by default Strict host-key verification for sandbox git transfers existed only as an opt-in (DAYTONA_SSH_KNOWN_HOSTS), so most users were on trust-on-first-use - the gap reported in #46 item 3. The Daytona API now publishes the gateway host key on /config (sshGatewayHost, sshGatewayPort, sshGatewayHostKeys), fetched over the same TLS channel the plugin already trusts for the SSH access token itself, so consuming it adds no new trust root while making strict verification the default. Trust is resolved once per process, in order: 1. DAYTONA_SSH_KNOWN_HOSTS set -> that file is the only trust root (manual). 2. Otherwise, unless DAYTONA_SSH_AUTO_PIN=false, the published key is written to a plugin-managed known_hosts file (0600, atomic) and used as the only trust root (pinned). The pin file, not the API, is what connections use, so an API outage after the first pin never weakens verification. 3. If nothing can be pinned (older API, API unreachable, auto-pin off), the SSH client's normal verification applies - the previous behavior. Pinning is fail-closed against change: if the API later publishes a key set that no longer contains the pinned key, transfers are refused with a message pointing at the security policy and the pin file, and no access token is minted. A rotation that publishes old and new keys together (as the security policy commits to) extends the pin without failing. The gateway endpoint is taken from the API as well, replacing the hardcoded hostname and producing [host]:port known_hosts entries for non-default ports. A manual file that disagrees with the published key logs a warning but is honored as configured. Docs: the README section now points at the published known_hosts line in the org-wide security policy instead of asking users to verify 'out of band' against nothing, and the repository README links the security policy for vulnerability reporting. Verified against the live gateway with a local /config double serving the real published key: auto-pinned strict push and pull succeed; a wrong pin is rejected by the gateway (the pin is the only trust root); a published key change refuses the transfer before any token is minted; rotation overlap extends the pin and the retired key drops out cleanly; API outage keeps an existing pin and otherwise degrades to inherited verification; DAYTONA_SSH_AUTO_PIN=false and DAYTONA_SSH_KNOWN_HOSTS behave as documented; inherited mode still transfers. Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
…d explain the dormant-client case
- Rename HostKeyTrust to HostKeyVerification. The type is the OUTPUT of
resolution - which known_hosts file, if any, is the trust root and where
it came from - not a trust toggle. No mode disables verification; the
weakest outcome ('inherited') is the SSH client's own behavior. The old
name read as a switch a user could flip.
- The key-change refusal now names the benign cause: a machine that has
not synced since before a rotation's overlap window sees only the new
key and cannot tell rotation from substitution. The message states this
and gives the exact recovery (verify against the policy, rm the pin
file, sync again).
Verified: all four rotation client states (active during overlap ->
extended; after retirement -> follows; dormant past the window -> refused
with the new message, old pin intact, recovers after rm; API down or
publishing nothing -> existing pin kept, never treated as a change), no
code path sets StrictHostKeyChecking=no, and live auto-pinned transfers
plus wrong-pin rejection against the real gateway still pass.
Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
…st-key verification Review follow-ups on auto-pin: - Neutralize every ssh_config directive that can add or redirect host-key trust when a pin is in force, so the pin file is the only trust root: KnownHostsCommand=none (a configured program could supply extra accepted keys), UpdateHostKeys=no (a connected server could append further keys to the pin file), VerifyHostKeyDNS=no (SSHFP records as a trust source), and HostKeyAlias fixed to the pinned host so the lookup cannot be redirected. All are first-value-wins options on a command the plugin owns. - Validate published keys and compute fingerprints in-process instead of shelling out to ssh-keygen: a missing or failing optional executable silently downgraded every API response to inherited verification. - Require an integer port from the API; a fractional value produced an invalid endpoint. - When the API is unavailable, recover the gateway endpoint from the pin file's own host field ([host]:port or bare host) instead of defaulting to ssh.app.daytona.io:22, which made every strict transfer against a non-default gateway fail while its pin named another endpoint. - README: manual-pin instructions now cover non-default host/port ([host]:port entries, ssh-keyscan -p), and the outage claim is scoped to machines that already hold a pin. Verified live: with a wrong key pinned and a hostile ~/.ssh/config supplying the real key via KnownHostsCommand plus StrictHostKeyChecking no, the gateway is still rejected; a correct auto-pin still transfers with the hardened option set and the pin file stays a single line after connecting; ssh-keygen removed from PATH no longer prevents pinning and the in-process fingerprint equals ssh-keygen's; fractional, string, zero and out-of-range ports fall back to 22; API outage after a non-default pin recovers host and port from the pin entry. Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the last piece of #46 item 3 on the plugin side. Strict host-key verification for sandbox git transfers existed only as an opt-in (
DAYTONA_SSH_KNOWN_HOSTS, #50), so most users were still on trust-on-first-use. The Daytona API now publishes the gateway host key on/config(daytona/daytona-ai#1043 —sshGatewayHost,sshGatewayPort,sshGatewayHostKeys), and this PR makes the plugin consume it so strict verification becomes the default.Fetching the key over the same TLS channel the plugin already trusts for the SSH access token itself adds no new trust root. What it removes is the first-connection window in which any network position could substitute the gateway.
Trust resolution (once per process)
DAYTONA_SSH_KNOWN_HOSTSsetDAYTONA_SSH_AUTO_PIN=falsestorage/daytona/gateway_known_hosts(0600, atomic)In both pinned modes:
UserKnownHostsFile=<pin>,GlobalKnownHostsFile=/dev/null,StrictHostKeyChecking=yes. The pin file, not the API, is what connections use — an API outage after the first pin never weakens verification.Fail-closed against change: if the API later publishes a key set that no longer contains the pinned key, transfers are refused with a message pointing at the security policy and the pin file, and no access token is minted. A rotation that publishes old + new together (as the policy commits to) extends the pin without failing anyone.
Also: the gateway endpoint now comes from the API (no more hardcoded
ssh.app.daytona.io), with[host]:portentries for non-22 ports; a manual file that disagrees with the published key logs a warning but is honored.Docs
known_hostsline in the org security policy instead of asking users to verify "out of band" against nothing; documents the three modes and the fail-closed message.SECURITY.mdwas removed in docs(security): defer to org-wide default security policy #53; this makes the inheritance visible to readers).Verification
Prod
/configdoes not serve the fields yet (pending daytona/daytona-ai#1043 + daytonaio/infrastructure-aws#419), so a local/configdouble served the real published key, and every transfer ran against the live gateway with a real sandbox — 23 assertions:ssh.app.daytona.ioHost key verification failed) — the pin is the only trust rootDAYTONA_SSH_AUTO_PIN=false→ inherited, nothing written;DAYTONA_SSH_KNOWN_HOSTS→ manual wins, disagreement only warns[host]:portentrytsc+ build cleanActivates automatically once the API fields are live; until then behavior is identical to today.
Summary by cubic
Strict SSH host-key verification for sandbox git transfers is now the default: the plugin fetches the gateway host key from the Daytona API and pins it in a plugin-managed
known_hostsfile over the same TLS channel already trusted for the SSH access token. Previously strict verification only happened whenDAYTONA_SSH_KNOWN_HOSTSwas set; otherwise transfers used trust-on-first-use.Behavior
StrictHostKeyChecking=yes, system-wide known hosts ignored, andKnownHostsCommand,UpdateHostKeys,VerifyHostKeyDNS, andHostKeyAliasneutralized so no other ssh_config directive can add or redirect trust; other SSH remotes are unaffected.ssh-keygenno longer silently downgrades verification, and non-integer API ports are rejected.DAYTONA_SSH_KNOWN_HOSTSstill wins (disagreement only logs a warning);DAYTONA_SSH_AUTO_PIN=falseor no published key (older API, unreachable) restores the SSH client's normal verification.rmrecovery.[host]:portentries for non-22 ports) comes from the API instead of the hardcodedssh.app.daytona.io; when the API is down after a non-default pin, it's recovered from the pin file's own host field.Rollout
sshGatewayHostKeys, behavior is unchanged, so this can merge before the API change ships.Written for commit c6b4f7c. Summary will update on new commits.